package app.authorization.manager;

import com.alibaba.fastjson.JSONObject;
import com.dj.gateway.app.authorization.dto.WhiteIpDto;
import com.dj.gateway.app.authorization.properties.SecurityProperties;
import com.dj.gateway.app.authorization.properties.WhiteIpProperties;
import com.dj.gateway.app.component.DJRedisTokenStore;
import com.dj.gateway.app.context.OAuth2AuthenticationContextHolder;
import com.dj.gateway.app.utils.IpUtils;
import com.dj.gateway.app.utils.JsonUtils;
import com.dj.gateway.app.utils.RedisUtil;
import com.dj.gateway.app.utils.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;


/**
 * 自定义授权管理器，核心配置
 *
 * @author 大仙
 */
@Slf4j
public class WebfluxReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    public static final String ANOTHER_ACCESS_TOKEN_FLAG = "sysConfig:another_access_token_flag";

    private SecurityProperties securityProperties;

    private WhiteIpProperties whiteIpProperties;

    private AntPathMatcher matcher = new AntPathMatcher();

    private DJRedisTokenStore redisTokenStore;

    public WebfluxReactiveAuthorizationManager(RedisConnectionFactory connectionFactory, SecurityProperties securityProperties, WhiteIpProperties whiteIpProperties) {
        redisTokenStore = new DJRedisTokenStore(connectionFactory);
        this.securityProperties = securityProperties;
        this.whiteIpProperties = whiteIpProperties;
    }

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) {
        //获取请求
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        //判断当前是否有接口权限
        String httpMethod = request.getMethod().name();
        log.debug("请求方法:{}", httpMethod);
        //如果是OPTIONS的请求直接放过
        if (HttpMethod.OPTIONS.name().equals(httpMethod)) {
            return Mono.just(new AuthorizationDecision(true));
        }
        if (!isWhiteIp(request)) {
            return Mono.just(new AuthorizationDecision(false));
        }
        // 判断是否白名单
        if (isIgnoredUrl(request)) {
            // 白名单请求地址的情况，若token有效，获取用户信息；否则忽略
            String accessToken = getAccessToken(request);
            if (!StringUtils.isBlank(accessToken)) {
                OAuth2Authentication oAuth2Authentication = redisTokenStore.readAuthentication(accessToken);
                if (oAuth2Authentication != null) {
                    OAuth2AuthenticationContextHolder.setUserAnthentication(oAuth2Authentication);
                }
            }
            return Mono.just(new AuthorizationDecision(true));
        } else {
            // 不是白名单的情况，若token不存在或无效，认证不通过
            String accessToken = getAccessToken(request);
            if (StringUtils.isBlank(accessToken)) {
                return Mono.just(new AuthorizationDecision(false));
            }
            OAuth2Authentication oAuth2Authentication = redisTokenStore.readAuthentication(accessToken);
            if (oAuth2Authentication != null) {
                OAuth2AuthenticationContextHolder.setUserAnthentication(oAuth2Authentication);
                return Mono.just(new AuthorizationDecision(true));
            } else {
                return Mono.just(new AuthorizationDecision(false));
            }
        }

    }

    private String getAccessToken(ServerHttpRequest request) {
        String accessToken = "";

        MultiValueMap<String, HttpCookie> cookies = request.getCookies();
        HttpCookie accessTokenCookie = cookies.getFirst("access_token");
        log.info("cookie获取access_token详情:{}",accessTokenCookie);
        if(getAnotherAccessTokenFlag(ANOTHER_ACCESS_TOKEN_FLAG)){
            accessTokenCookie = cookies.getFirst("another_access_token");
            log.info("cookie获取another_access_token详情:{}",accessTokenCookie);
        }
        if (accessTokenCookie != null) {
            accessToken = accessTokenCookie.getValue();
            log.info("cookie获取详情:{}",accessToken);
        }

        if (StringUtils.isBlank(accessToken)) {
            accessToken = request.getHeaders().getFirst("access_token");
            log.info("header获取access_token详情:{}",accessToken);
        }

        if (StringUtils.isBlank(accessToken)) {
            accessToken = request.getQueryParams().getFirst("access_token");
            log.info("Params获取access_token详情:{}",accessToken);
        }
        if (StringUtils.isBlank(accessToken)) {
            accessTokenCookie = cookies.getFirst("access_token");
            if(accessTokenCookie!=null){
                accessToken = accessTokenCookie.getValue();
                log.info("cookie获取access_token详情:{}",accessToken);
            }
        }
        return accessToken;
    }


    private Boolean getAnotherAccessTokenFlag(String key){
        boolean result = false;
        try {
            RedisUtil redisUtil = SpringUtils.getApplicationContext().getBean("redisUtil", RedisUtil.class);
            String value = redisUtil.get(ANOTHER_ACCESS_TOKEN_FLAG);
            String configValue = JSONObject.parseObject(value).getString("configValue");
            result = Boolean.parseBoolean(configValue);
        }catch (Exception e){
            log.error("redis缓存布尔类型转换出错!",e);
        }
        return result;
    }

    private boolean isIgnoredUrl(ServerHttpRequest request) {
        String url = request.getPath().value();
        log.debug("请求url:{}", url);
        log.debug("白名单：" + JsonUtils.toJson(securityProperties.getIgnoreUrl()));
        // 不拦截的请求
        for (String path : securityProperties.getIgnoreUrl()) {
            String temp = path.trim();
            if (matcher.match(temp, url)) {
                return true;
            }
        }
        return false;
    }

    private boolean isWhiteIp(ServerHttpRequest request) {
        String url = request.getPath().value();
        String clientIp = IpUtils.getClientIp(request);
        log.info("APP网关请求url:{},入口ip地址:{}", url, clientIp);
        log.info("已配置的ip访问白名单：" + JsonUtils.toJson(whiteIpProperties.getList()));
        List<WhiteIpDto> list = whiteIpProperties.getList();
        if (CollectionUtils.isEmpty(list)) {
            return true;
        }
        WhiteIpDto whiteIpDto = list.stream().filter(x -> url.equalsIgnoreCase(x.getUrl().trim())).findFirst().orElse(null);
        if (whiteIpDto != null) {
            if (StringUtils.isNotBlank(whiteIpDto.getWhiteIp())) {
                List<String> ipList = Arrays.asList(whiteIpDto.getWhiteIp().split(","));
                if (!ipList.contains(clientIp)) {
                    log.info("网关ip白名单认证失败,接口url:{},ip:{}", url, clientIp);
                    return false;
                }
            }
        }
        return true;
    }

//    /**
//     * 获取当前用户的权限集合
//     * @param authority
//     * @return
//     */
//    private List<Permission> getPermissions(String authority){
//        String redisKey = Constant.PERMISSIONS+authority;
//        long size = permissionRedisTemplate.opsForList().size(redisKey);
//        List<Permission> permissions = permissionRedisTemplate.opsForList().range(redisKey, 0, size);
//        return permissions;
//    }

//    public void setIgnored(String ignored) {
//        ignored = org.springframework.util.StringUtils.trimAllWhitespace(ignored);
//        if (ignored != null && !"".equals(ignored)) {
//            this.ignoreds = ignored.split(",");
//        } else {
//            this.ignoreds = new String[]{};
//        }
//    }

//    public void setNotRole(String notRole) {
//        notRole = org.springframework.util.StringUtils.trimAllWhitespace(notRole);
//        if (notRole != null && !"".equals(notRole)) {
//            this.notRoles = notRole.split(",");
//        } else {
//            this.notRoles = new String[]{};
//        }
//    }

//    /**
//     * 构造对象
//     */
//    @Data
//    class AuthUser{
//        private String authority;
//
//        private BaseUser baseUser;
//    }

//    @Bean("tokenStore")
//    public TokenStore tokenStore() {
//        DJRedisTokenStore redis = new DJRedisTokenStore(connectionFactory);
//        redis.setClientDetailsService(clientDetailsService);
//        return redis;
//    }


}
